package com.netx.generics.basic;
import java.util.Comparator;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import com.netx.generics.util.Type;
import com.netx.generics.util.Tools;


public class PropertyDefinition {

	// TYPE:
	public static final Object NO_DEFAULT_VALUE = new Object();
	
	public static Comparator<Object> getIgnoreCaseComparator() {
		return new IgnoreCaseComparator();
	}
	
	private static class IgnoreCaseComparator implements Comparator<Object> {
		public int compare(Object o1, Object o2) {
			// Force cast:
			String s1 = (String)o1;
			String s2 = (String)o2;
			if(s1.equalsIgnoreCase(s2)) {
				return 0;
			}
			else {
				return s1.compareTo(s2);
			}
		}
	}

	private static class RegularComparator implements Comparator<Object> {
		public int compare(Object o1, Object o2) {
			if(o1.equals(o2)) {
				return 0;
			}
			else {
				return -1;
			}
		}
	}
	
	// INSTANCE:
	private final String _propertyName;
	private final Type _type;
	private final boolean _isReadOnly;
	private final boolean _mayBeNull;
	private final Object _default;
	private final Set<Object> _range;
	private final Comparator<Object> _comparator;

	
	public PropertyDefinition(String propertyName, Class<?> c, boolean isReadOnly, boolean mayBeNull, Object defaultValue, Object[] valueRange, Comparator<Object> comparator) {
		Checker.checkTextIdentifier(propertyName, "propertyName", true);
		Checker.checkNull(c, "c");
		Checker.checkNull(valueRange, "valueRange");
		_propertyName = propertyName;
		_type = new Type(c);
		_isReadOnly = isReadOnly;
		_mayBeNull = mayBeNull;
		_default = defaultValue;
		if(comparator == null) {
			_comparator = new RegularComparator();
		}
		else {
			_comparator = comparator;
		}
		// Convert valueRange:
		_range = new HashSet<Object>();
		for(Object o : valueRange) {
			if(o == null) {
				if(!_range.add(o)) {
					throw new IllegalArgumentException("repeated valueRange value: '"+o+"'");
				}
			}
			else if(Tools.contains(_range, o, _comparator)) {
				throw new IllegalArgumentException("repeated valueRange value: '"+o+"'");
			}
			else {
				_range.add(o);
			}
		}
		// Check defaultValue:
		if(_default != null && _default != NO_DEFAULT_VALUE) {
			if(!_type.getObjectClass().isAssignableFrom(_default.getClass())) {
				throw new IllegalArgumentException("illegal defaultValue class: "+defaultValue.getClass().getName()+" (expected "+_type.getName()+")");
			}
		}
		if(!mayBeNull) {
			if(_default == null) {
				throw new IllegalArgumentException("null default value specified when mayBeNull is false");
			}
			if(_range.contains(null)) {
				throw new IllegalArgumentException("null valueRange element specified when mayBeNull is false");
			}
		}
		// Check valueRange:
		if(_range.size() > 0) {
			// Check valueRange Class:
			Iterator<? extends Object> it = _range.iterator();
			while(it.hasNext()) {
				Object o = it.next();
				if(o != null) {
					if(!_type.getObjectClass().isAssignableFrom(o.getClass())) {
						throw new IllegalArgumentException("illegal valueRange class: "+o.getClass().getName()+" (expected "+_type.getName()+")");
					}
				}
			}
			// Check if defaultValue is in valueRange:
			boolean found = false;
			if(_default == null && _range.contains(null)) {
				found = true;
			}
			else if(_default != NO_DEFAULT_VALUE) {
				for(Object o : _range) {
					if(o != null) {
						if(_comparator.compare(o, defaultValue) == 0) {
							found = true;
							break;
						}
					}
				}
			}
			if(!found) {
				throw new IllegalArgumentException("illegal default value: '"+defaultValue+"' (not found in value range)");
			}
		}
	}

	public PropertyDefinition(String propertyName, Class<?> c, boolean isReadOnly, boolean mayBeNull, Object defaultValue, Object[] valueRange) {
		this(propertyName, c, isReadOnly, mayBeNull, defaultValue, valueRange, new RegularComparator());
	}

	public PropertyDefinition(String propertyName, Class<?> c, boolean isReadOnly, boolean mayBeNull, Object defaultValue) {
		this(propertyName, c, isReadOnly, mayBeNull, defaultValue, new Object[0], new RegularComparator());
	}
	
	public String getPropertyName() {
		return _propertyName;
	}
	
	// For Context:
	void validate(Object value, Property oldProperty) {
		if(_isReadOnly && oldProperty != null) {
			throw new ValidationException("the property is read-only and cannot be changed");
		}
		if(value == null) {
			if(!_mayBeNull) {
				throw new ValidationException("property cannot be null");
			}
			if(!_range.contains(null)) {
				throw new ValidationException("property does not allow value 'null'");
			}
		}
		else {
			if(!_type.getObjectClass().isAssignableFrom(value.getClass())) {
				throw new ValidationException("expected value of type "+_type.getName()+", found "+value.getClass().getName());
			}
			if(_range.size() > 0) {
				if(!Tools.contains(_range, value, _comparator)) {
					throw new ValidationException("property does not allow value '"+value+"'");
				}
			}
		}
	}
	
	// For Context:
	boolean isMandatory() {
		return _default == NO_DEFAULT_VALUE;
	}
	
	// For Context:
	Object getDefaultValue() {
		return _default;
	}

}
